﻿using UnityEngine;

namespace Learning
{
    public class SimpleGPUInstancing : MonoBehaviour
    {
        public int instanceCount = 100000;
        public Mesh instanceMesh;
        public Material instanceMaterial;
        public int subMeshIndex = 0;

        private int cachedInstanceCount = -1;
        private int cachedSubMeshIndex = -1;
        private ComputeBuffer positionBuffer;
        private ComputeBuffer argsBuffer;
        private uint[] args = new uint[5] {0, 0, 0, 0, 0};

        void Start()
        {
            // 带参数的缓冲区 bufferWithArgs 必须在给定的 argsOffset 偏移处具有五个整数： 每个实例的索引数、实例数、起始索引位置、基顶点位置、起始实例位置
            argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
            
            UpdateBuffers();
        }

        void Update()
        {
            // 数量或subMesh改变时，更新传入的缓冲
            if (cachedInstanceCount != instanceCount || cachedSubMeshIndex != subMeshIndex)
                UpdateBuffers();

            // 点击按键控制创建数量
            if (Input.GetAxisRaw("Horizontal") != 0.0f)
                instanceCount = (int) Mathf.Clamp(instanceCount + Input.GetAxis("Horizontal") * 40000, 1.0f, 5000000.0f);

            // 调用方法进行渲染
            // 仅当网格中的子网格具有不同拓扑（例如，三角形和线条）时，Unity 才需要 submeshIndex 参数。否则，有关要绘制哪个子网格的所有信息都将来自 bufferWithArgs 参数
            Graphics.DrawMeshInstancedIndirect(instanceMesh, subMeshIndex, instanceMaterial, new Bounds(Vector3.zero, new Vector3(100.0f, 100.0f, 100.0f)), argsBuffer);
        }

        void OnGUI()
        {
            GUI.Label(new Rect(265, 25, 200, 30), "Instance Count: " + instanceCount.ToString());
            instanceCount = (int) GUI.HorizontalSlider(new Rect(25, 20, 200, 30), (float) instanceCount, 1.0f, 5000000.0f);
        }

        void UpdateBuffers()
        {
            if (instanceMesh != null)
                subMeshIndex = Mathf.Clamp(subMeshIndex, 0, instanceMesh.subMeshCount - 1);

            // 创建每个Instancing的位置信息buffer
            if (positionBuffer != null)
                positionBuffer.Release();
            positionBuffer = new ComputeBuffer(instanceCount, 16);
            Vector4[] positions = new Vector4[instanceCount];
            for (int i = 0; i < instanceCount; i++)
            {
                float angle = Random.Range(0.0f, Mathf.PI * 2.0f);
                float distance = Random.Range(20.0f, 100.0f);
                float height = Random.Range(-2.0f, 2.0f);
                float size = Random.Range(0.05f, 0.25f);
                positions[i] = new Vector4(Mathf.Sin(angle) * distance, height, Mathf.Cos(angle) * distance, size);
            }
            positionBuffer.SetData(positions);
            
            instanceMaterial.SetBuffer("positionBuffer", positionBuffer);
            
            if (instanceMesh != null)
            {
                args[0] = (uint) instanceMesh.GetIndexCount(subMeshIndex); // 每个实例的索引数
                args[1] = (uint) instanceCount; // 实例数
                args[2] = (uint) instanceMesh.GetIndexStart(subMeshIndex); // 起始索引位置
                args[3] = (uint) instanceMesh.GetBaseVertex(subMeshIndex); // 基顶点位置
            } else
            {
                args[0] = args[1] = args[2] = args[3] = 0;
            }

            argsBuffer.SetData(args);

            cachedInstanceCount = instanceCount;
            cachedSubMeshIndex = subMeshIndex;
        }

        void OnDisable()
        {
            if (positionBuffer != null)
                positionBuffer.Release();
            positionBuffer = null;

            if (argsBuffer != null)
                argsBuffer.Release();
            argsBuffer = null;
        }
    }
}
